/**
 * Yarbu is a proof of concept of a JavaScript HTTP server.
 * Copyright (C) 2011 Antonio Hernandez Diaz <ahdiaz@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $Id: mvc.js 2 2011-01-08 06:03:19Z ahdiaz $
 */

var Sys = (function() {

	var Sys = {
		
		APP_PATH: null,
		MVC_PATH: null,
		EXTENSIONS_PATH: null,
		RENDERER_PATH: null,
		MODEL_PATH: null,
		VIEW_PATH: null,
		CONTROLLER_PATH: null,
		LAYOUT_PATH: null,
		DATA_PATH: null,
		ASSETS_URL: null,

		_controller: {},
		_model: {},
		_renderer: {},
		_dbconn: null,

		contentTypes: {
			html: 'text/html',
			xml: 'text/xml',
			json: 'application/x-json'
		},

		statusCodes: {
			'200': 'OK',
			'204': 'No Content',
			'302': 'Found',
			'303': 'See Other',
			'400': 'Bad Request',
			'401': 'Unauthorized',
			'403': 'Forbidden',
			'404': 'Not Found'
		},

		createController: function(name, prototype) {
			Sys._controller[name.toLowerCase()] = klass(Sys.Controller, prototype);
		},

		createModel: function(name, prototype) {
			Sys._model[name] = klass(Sys.Model, prototype);
		},

		createRenderer: function(name, prototype) {
			Sys._renderer[name] = klass(Sys.Renderer, prototype);
		},

		Application: klass({

			config: null,

			_init: function(config) {

				this.config = extend({
					appPath: '.',
					defaultController: 'site',
					defaultAction: 'index',
					defaultLayout: 'site',
					viewExtension: 'tpl',
					defaultContentType: 'html',
					renderer: 'trimpath',
					extensions: [],
					database: {
						driver: null,
						url: null
					}
				}, config);

				Sys.APP_PATH = this.config.appPath;
				Sys.MVC_PATH = printf('%s/mvc', Sys.APP_PATH);
				Sys.EXTENSIONS_PATH = printf('%s/ext', Sys.MVC_PATH);
				Sys.RENDERER_PATH = printf('%s/renderer', Sys.MVC_PATH);
				Sys.MODEL_PATH = printf('%s/model', Sys.APP_PATH);
				Sys.VIEW_PATH = printf('%s/view', Sys.APP_PATH);
				Sys.CONTROLLER_PATH = printf('%s/controller', Sys.APP_PATH);
				Sys.LAYOUT_PATH = printf('%s/layout', Sys.VIEW_PATH);
				Sys.DATA_PATH = printf('%s/data', Sys.APP_PATH);
				Sys.ASSETS_URL = printf('%s%s/assets', Request.rootURL, basedir(Request.requestURI));
				
				this.loadExtensions();
				this.databaseConnection();
			},
			
			free: function() {
				// free resources here
				if (Sys._dbconn !== null) {
					Sys._dbconn.close();
					Sys._dbconn = null;
				}
			},
			
			databaseConnection: function() {
				var db = this.config.database;
				if (db.driver && db.url) {
					Sys._dbconn = new ConnectionFactory(db.driver).getConnection(db.url);
				}
			},

			loadExtensions: function() {
				for (var i=0, l=this.config.extensions.length; i<l; i++) {
					load(printf('%s/%s', Sys.EXTENSIONS_PATH, this.config.extensions[i]));
				}
			},

			dispatch: function() {

				try {

					var request = this.getRequest();					
					var c = this.getController(request);

					if (!c[request.action]) {
						throw new Sys.NotFoundException(printf('Action %s::%s not found',
							request.controllerClass,
							request.action
						));
					}

					c[request.action]();
					throw new Sys.NoContentException();

				} catch(e) {
					
					this.output(e);
				}
			},

			getRequest: function() {

				var r = Params.r;
				if (r == null) {
					r = printf('%s/%s', this.config.defaultController, this.config.defaultAction);
				}

				var a = r.split('/');
				if (!a[1]) {
					a[1] = this.config.defaultAction;
				}
				
				return {
					controllerName: a[0].toLowerCase(),
					controllerClass: capitalize(a[0].toLowerCase()) + 'Controller',
					action: a[1].toLowerCase() + 'Action'
				}
			},

			getController: function(request) {

				var c = Sys._controller[request.controllerName];

				if (c != undefined) {
					return new c(this.config, request);
				}

				var controllerPath = printf('%s/%s.js', Sys.CONTROLLER_PATH, request.controllerClass);

				try {

					load(controllerPath);
					c = Sys._controller[request.controllerName];
					c = new c(this.config, request);

				} catch(e) {

					throw new Sys.NotFoundException(printf('Controller %s not found', request.controllerClass));
				}

				return c;
			},

			output: function(e) {

				e = Sys.Exception.importException(e);

				var code = e.getCode();
				var msg = e.getMessage();
				var headers = e.getHeaders();

				if (!headers['Content-type']) {
					headers['Content-type'] = Sys.contentTypes[this.config.defaultContentType];
				}

				Response.status = code;
				for (var o in headers) {
					Response.headers[o] = headers[o];
				}

				Response.body.push(msg);
				this.free();
			}
		}),

		Controller: klass({

			config: null,
			request: null,

			_init: function(config, request) {
				this.config = config;
				this.request = request;
			},

			getModel: function(model) {

				var m = Sys._model[model];

				if (m != undefined) {
					return new m();
				}

				var modelPath = printf('%s/%s.js', Sys.MODEL_PATH, model);

				try {

					load(modelPath);
					m = Sys._model[model];
					m = new m();

				} catch(e) {

					throw new Sys.NotFoundException(printf('Model %s not found', model));
				}

				return m;
			},

			getRenderer: function() {

				var rName = capitalize(this.config.renderer);
				var r = Sys._renderer[rName];

				if (r != undefined) {
					return new r();
				}

				var rendererPath = printf('%s/%sRenderer.js', Sys.RENDERER_PATH, rName);

				try {

					load(rendererPath);
					r = Sys._renderer[rName];
					r = new r();

				} catch(e) {

					throw new Sys.NotFoundException(printf('Renderer %s not found', rName));
				}

				return r;
			},

			_render: function(viewPath, values) {

				var viewStr = readFile(viewPath);
				var r = this.getRenderer();

				try {

					viewStr = r.process(viewStr, values);
				} catch(e) {

					throw e;
				}

				return viewStr;
			},

			render: function(view, values, contentType, ret) {

				var viewStr = null;
				var contentTypeStr = Sys.contentTypes[contentType] || Sys.contentTypes[this.config.defaultContentType];
				var contentStr = null;
				values = extend({}, values, {Sys: Sys})

				try {

					contentStr = this.renderPartial(view, values, contentType, true);
				} catch(e) {

					throw e;
				}

				try {

					viewStr = this._render(
						printf('%s/%s.%s',
							Sys.LAYOUT_PATH,
							this.config.defaultLayout,
							this.config.viewExtension
						),
						extend(values, {content: contentStr})
					);

				} catch(e) {

					e = Sys.Exception.importException(e);
					var code = e.getCode();

					if (code == -404) {
						throw new Sys.NotFoundException(printf('Layout %s not found', this.config.defaultLayout));
					} else if (code == 404) {
						throw new Sys.NotFoundException(printf('Renderer %s not found', this.config.renderer));
					} else {
						throw e;
					}
				}

				if (ret === true) {
					return viewStr;
				} else {
					throw new Sys.OkException(viewStr, {'Content-type': contentTypeStr});
				}
			},

			renderPartial: function(view, values, contentType, ret) {

				var viewStr = null;
				var contentTypeStr = Sys.contentTypes[contentType] || Sys.contentTypes[this.config.defaultContentType];

				try {

					viewStr = this._render(
						printf('%s/%s/%s.%s',
							Sys.VIEW_PATH,
							this.request.controllerName,
							view,
							this.config.viewExtension
						),
						values
					);

				} catch(e) {

					e = Sys.Exception.importException(e);
					var code = e.getCode();

					if (code == -404) {
						throw new Sys.NotFoundException(printf('View %s/%s not found', this.request.controllerName, view));
					} else if (code == 404) {
						throw new Sys.NotFoundException(printf('Renderer %s not found', this.config.renderer));
					} else {
						throw e;
					}
				}

				if (ret === true) {
					return viewStr;
				} else {
					throw new Sys.OkException(viewStr, {'Content-type': contentTypeStr});
				}
			},

			redirect: function(action, params) {

				var qs = 'r=' + action;
				for (var o in params) {
					qs += printf('&%s=%s', o, params[o]);
				}

				var headers = {Location: printf('%s?%s', Request.requestURL, qs)};
				throw new Sys.SeeOtherException('', headers);
			}
		}),

		Renderer: klass({
			_init: function() {
			},
			process: function(template, values) {
			}
		}),

		Model: klass({

			model: null,

			_init: function() {
				if (typeof(this.model) != 'object') this.model = {};
			},

			get: function(name) {
				return (this.model[name] != undefined) ? this.model[name] : undefined;
			},

			set: function(name, value) {
				if (typeof(this.model[name]) == 'undefined') return;
				this.model[name] = value;
			},

			getAttributes: function() {
				return this.model;
			},

			setAttributes: function(attrs) {
				for (var o in attrs) {
					this.set(o, attrs[o]);
				}
			}
		}),

		Exception: klass({

			_static: {
				importException: function(e) {

					if (e.getCode && e.getMessage && e.getHeaders) {
						return e;
					}

					var code = 200;
					var message = '';
					var headers = {};

					if (typeof(e) == 'object' && e.message) {

						message = e.message;

					} else if (typeof(e) == 'string') {

						message = e;

					} else if (typeof(e) == 'number') {

						code = e;
					}

					return new Sys.Exception(code, message, headers);
				}
			},
			_public: {
				code: null,
				message: null,
				headers: null,

				_init: function(code, message, headers) {

					this.code = code || 200;
					this.message = message || '';
					this.headers = headers || {};
				},

				getCode: function() {
					return this.code;
				},

				getMessage: function() {
					return this.message;
				},

				getHeaders: function() {
					return this.headers;
				},

				toString: function() {
					return this.code + ': ' + this.message;
				}
			}
		})
	};

	Sys = extend(Sys, {

		FileNotFoundException: klass(Sys.Exception, {
			_init: function(message, headers) {
				this.code = -404;
				this.message = message || '';
				this.headers = headers || {};
			}
		}),

		OkException: klass(Sys.Exception, {
			_init: function(message, headers) {
				this.code = 200;
				this.message = message || '';
				this.headers = headers || {};
			}
		}),

		NoContentException: klass(Sys.Exception, {
			_init: function(message, headers) {
				this.code = 204;
				this.message = message || '';
				this.headers = headers || {};
			}
		}),

		SeeOtherException: klass(Sys.Exception, {
			_init: function(message, headers) {
				// 302 = Found, 303 = See Other
				this.code = Request.protocol == 'HTTP/1.0' ? 302 : 303;
				this.message = message || '';
				this.headers = headers || {};
			}
		}),

		BadRequestException: klass(Sys.Exception, {
			_init: function(message, headers) {
				this.code = 400;
				this.message = message || '';
				this.headers = headers || {};
			}
		}),

		UnauthorizedException: klass(Sys.Exception, {
			_init: function(message, headers) {
				this.code = 401;
				this.message = message || '';
				this.headers = headers || {};
			}
		}),

		ForbiddenException: klass(Sys.Exception, {
			_init: function(message, headers) {
				this.code = 403;
				this.message = message || '';
				this.headers = headers || {};
			}
		}),

		NotFoundException: klass(Sys.Exception, {
			_init: function(message, headers) {
				this.code = 404;
				this.message = message || '';
				this.headers = headers || {};
			}
		})
	});

	return Sys;

})();
